package school.valon.business.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import org.springframework.transaction.annotation.Transactional;

import school.valon.business.UserBusiness;
import school.valon.business.base.BaseBusiness;
import school.valon.constantes.EnumCommentType;
import school.valon.constantes.EnumStaffFunction;
import school.valon.constantes.EnumUserStatus;
import school.valon.constantes.EnumUserType;
import school.valon.dto.AddressDto;
import school.valon.dto.CourseDto;
import school.valon.dto.CourseOfferingDto;
import school.valon.dto.CourseOfferingStudentEnrolmentDto;
import school.valon.dto.OutsideDonatorDto;
import school.valon.dto.ParentDto;
import school.valon.dto.StaffAttendanceDto;
import school.valon.dto.StaffDto;
import school.valon.dto.StaffFunctionDto;
import school.valon.dto.StudentClassRecordingCommentDto;
import school.valon.dto.StudentClassRecordingDto;
import school.valon.dto.StudentDto;
import school.valon.dto.UserDto;
import school.valon.persistence.dao.AddressDao;
import school.valon.persistence.dao.OutsideDonatorDao;
import school.valon.persistence.dao.ParentDao;
import school.valon.persistence.dao.StaffDao;
import school.valon.persistence.dao.StudentDao;
import school.valon.persistence.dao.UserDao;
import school.valon.persistence.pojo.Address;
import school.valon.persistence.pojo.CourseOffering;
import school.valon.persistence.pojo.CourseOfferingStudentEnrolment;
import school.valon.persistence.pojo.OutsideDonator;
import school.valon.persistence.pojo.Parent;
import school.valon.persistence.pojo.Staff;
import school.valon.persistence.pojo.StaffAttendance;
import school.valon.persistence.pojo.StaffFunction;
import school.valon.persistence.pojo.Student;
import school.valon.persistence.pojo.StudentClassRecording;
import school.valon.persistence.pojo.StudentClassRecordingComment;
import school.valon.persistence.pojo.User;
import school.valon.persistence.pojo.UserStatus;

@Transactional
public class UserBusinessImpl extends BaseBusiness implements UserBusiness {

	private UserDao userDao;
	private ParentDao parentDao;
	private StudentDao studentDao;
	private OutsideDonatorDao outsideDonatorDao;
	private StaffDao staffDao;
	private AddressDao addressDao;

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
	
	public void setParentDao(ParentDao parentDao) {
		this.parentDao = parentDao;
	}

	public void setStudentDao(StudentDao studentDao) {
		this.studentDao = studentDao;
	}

	public void setOutsideDonatorDao(OutsideDonatorDao outsideDonatorDao) {
		this.outsideDonatorDao = outsideDonatorDao;
	}

	public void setStaffDao(StaffDao staffDao) {
		this.staffDao = staffDao;
	}

	public void setAddressDao(AddressDao addressDao) {
		this.addressDao = addressDao;
	}

	public UserDto registerUser(UserDto userDto) {
		User newUser;
		UserStatus userStatus = userDao.findUserStatusById(EnumUserStatus.INACTIVE.getStatusId());
		EnumUserType enumUserType = EnumUserType.getUserTypeById(userDto.getUserType().getUserTypeId());

		//TODO Generating validation key
		//TODO send a message to the user using his email

		switch (enumUserType) {
		case STUDENT:
			StudentDto studentDto = mapper.map(userDto, StudentDto.class);
			Student student = mapper.map(studentDto, Student.class);
			if (studentDto.getFather() != null) {
				student.setFather(parentDao.find(studentDto.getFather().getParentId()));
			} else {
				if (studentDto.getMother() != null) {
					student.setMother(parentDao.find(studentDto.getMother().getParentId()));
				}
			}
			student.setUserStatus(userStatus);
			newUser = studentDao.create(student);
			break;
		case PARENT:
			Parent parent = mapper.map(userDto, Parent.class);
			parent.setUserStatus(userStatus);
			newUser = parentDao.create(parent);
			break;
		case OUTSIDE_DONATOR:
			OutsideDonator outsideDonator = mapper.map(userDto, OutsideDonator.class);
			outsideDonator.setUserStatus(userStatus);
			newUser = outsideDonatorDao.create(outsideDonator);
			break;
		case STAFF:
			Staff staff = mapper.map(userDto, Staff.class);
			staff.setUserStatus(userStatus);
			newUser = staffDao.create(staff);
			break;
		default:
			newUser = userDao.create(mapper.map(userDto, User.class));
			break;
		}
		UserDto newUserDto = mapper.map(newUser, UserDto.class);
		newUserDto.getUserStatus().setUsers(null);
		if (newUserDto.getUserType() != null) newUserDto.getUserType().setUsers(null);
		newUserDto.setPayments(null);
		return newUserDto;
	}

	public boolean updateUser(UserDto userDto) {
		User user = null;
		if (userDto.getUserId() != null) {
			user = userDao.find(userDto.getUserId());
		} else if (userDto.getEmail() != null && !"".equals(userDto.getEmail().trim())) {
			user = userDao.findUserByEmail(userDto.getEmail());
		} else {
			return false;
		}
		updateUser(user, userDto);
		user.setFirstName(userDto.getFirstName());
		user.setLastName(userDto.getLastName());
		user.setGender(userDto.getGender());
		user.setEmail(userDto.getEmail());
		user.setPassword(userDto.getPassword());
		user.setValidationKey(userDto.getValidationKey());
		userDao.update(user);
		return true;
	}

	private void updateUser(User user, UserDto newUserDto) {
		EnumUserType enumUserType = EnumUserType.getUserTypeById(newUserDto.getUserType().getUserTypeId());
			switch (enumUserType) {
			case STUDENT:
				Student student = (Student) user;
				StudentDto studentDto = mapper.map(newUserDto, StudentDto.class);;
				student.setBirthDate(studentDto.getBirthDate());
				student.setHealthConcerns(studentDto.getHealthConcerns());
				student.setHealthInsuranceNumber(studentDto.getHealthInsuranceNumber());
				studentDao.update(student);
				break;
			case PARENT:
				Parent parent = (Parent) user;
				ParentDto parentDto = mapper.map(newUserDto, ParentDto.class);
				parent.setEmergencyContact(parentDto.getEmergencyContact());
				if (parentDto.getAddress() != null) {
					Address address = parent.getAddress();
					AddressDto newAddressDto = parentDto.getAddress();
					if (address == null) {
						address = new Address();
						mapper.map(newAddressDto, address);
						addressDao.create(address);
						parent.setAddress(address);
					} else {
						mapper.map(newAddressDto, address);
						addressDao.update(address);
					}
				}
				parentDao.update(parent);
				break;
			case OUTSIDE_DONATOR:
				outsideDonatorDao.update((OutsideDonator) user);
				break;
			case STAFF:
				Staff staff = (Staff) user;
				StaffDto staffDto = mapper.map(newUserDto, StaffDto.class);
				staff.setEmergencyContact(staffDto.getEmergencyContact());
				if (staffDto.getAddress() != null) {
					Address address = staff.getAddress();
					AddressDto newAddressDto = staffDto.getAddress();
					if (address == null) {
						address = new Address();
						mapper.map(newAddressDto, address);
						addressDao.create(address);
						staff.setAddress(address);
					} else {
						mapper.map(newAddressDto, address);
						addressDao.update(address);
					}
				}
				staffDao.update(staff);
			default:
				break;
			}
			;
	}

	public UserDto getUserDtoByEmail(String userEmail) {
		User user = userDao.findUserByEmail(userEmail);
		UserDto userDto;
		if (user instanceof Student) {
			userDto = mapper.map(user, StudentDto.class);
		} else if (user instanceof Parent) {
			userDto = mapper.map(user, ParentDto.class);
		} else if (user instanceof Staff) {
			userDto = mapper.map(user, StaffDto.class);
		} else if (user instanceof OutsideDonator) {
			userDto = mapper.map(user, OutsideDonatorDto.class);
		} else {
			userDto = mapper.map(user, UserDto.class);
		}
		return userDto;
	}
	
	public void deleteUser(Integer userId) {
		userDao.delete(userId);
	}
	
	public void linkParentToStudents(Integer parentId, List<StudentDto> studentDtoList) {
		Parent parent = parentDao.find(parentId);
		ParentDto parentDto = mapper.map(parent, ParentDto.class);
		for (StudentDto studentDtoIt : studentDtoList) {
			UserDto userDto = studentDtoIt;
			userDto.setUserType(EnumUserType.STUDENT.getUserTypeDto());
			if ("M".equalsIgnoreCase(parent.getGender())) {
				studentDtoIt.setFather(parentDto);
			} else {
				studentDtoIt.setMother(parentDto);
			}
			registerUser(userDto);
		}
	}

	public void updateStaffRoles(Integer staffId, List<EnumStaffFunction> staffFunctionList) {
		Staff staff = staffDao.find(staffId);
		List<StaffFunction> newStaffFuncionList = new ArrayList<StaffFunction>(staffFunctionList.size());
		for (EnumStaffFunction staffFunctionIt : staffFunctionList) {
			newStaffFuncionList.add(staffDao.findStaffFunctionById(staffFunctionIt.getFunctionId()));
		}
		staff.setStaffFunctions(newStaffFuncionList);
		staffDao.update(staff);
	}

	public StaffAttendanceDto saveStaffAttendance(StaffAttendanceDto staffAttendanceDto) {
		StaffAttendance staffAttendance = null;
		if (staffAttendanceDto.getStaffAttendanceId() == null) {
			Staff staff = null;
			if (staffAttendanceDto.getStaff() != null && staffAttendanceDto.getStaff().getStaffId() != null) {
				staff = staffDao.find(staffAttendanceDto.getStaff().getStaffId());
			}
			staffAttendance = mapper.map(staffAttendanceDto, StaffAttendance.class);
			staffAttendance.setStaff(staff);
			staffAttendance = staffDao.createStaffAtendance(staffAttendance);
		} else {
			staffAttendance = staffDao.findStaffAtendance(staffAttendanceDto.getStaffAttendanceId());
			staffAttendance.setAttendanceDate(staffAttendanceDto.getAttendanceDate());
			staffAttendance.setAttendedHours(staffAttendanceDto.getAttendedHours());
			staffDao.updateStaffAtendance(staffAttendance);
		}
		StaffAttendanceDto newStaffAttendanceDto = mapper.map(staffAttendance, StaffAttendanceDto.class);
		if (newStaffAttendanceDto.getStaff() != null) {
			newStaffAttendanceDto.getStaff().setStaffAttendances(null);
			newStaffAttendanceDto.getStaff().setStaffFunctions(null);
			newStaffAttendanceDto.getStaff().setSurveyQuestions(null);
			newStaffAttendanceDto.getStaff().setSurveyAnswers(null);
			newStaffAttendanceDto.getStaff().getUserStatus().setUsers(null);
		}
		return newStaffAttendanceDto;
	}

	public void deleteStaffAttendance(Integer staffAttendanceId) {
		staffDao.deleteStaffAttendance(staffAttendanceId);
	}

	@Transactional(readOnly=true)
	public StaffDto getStaffReportPerformance(Integer staffId) {

		Staff staff = null;
		if (staffId != null) {
			staff = staffDao.find(staffId);
		}
		if (staff == null) {
			return null;
		}
		StaffDto staffDto = new StaffDto();
		/*
		First Name,
		Last Name,
		Gender,
		emergency Contact,
		Address,
		Role,
		Attended Hours,
		Attended Dates,
		Courses Offered,
		Average Students up to date Total Marks in each offered course and Parent
		or Gaurdian's Complaint Reports against the respective staff.*/
		staffDto.setFirstName(staff.getFirstName());
		staffDto.setLastName(staff.getLastName());
		staffDto.setGender(staff.getGender());
		staffDto.setEmergencyContact(staff.getEmergencyContact());

		if (staff.getAddress() != null) {
			staff.getAddress().setParents(null);
			staff.getAddress().setSchools(null);
			staffDto.setAddress(mapper.map(staff.getAddress(), AddressDto.class));
			staffDao.detach(staff.getAddress());
		}

		List<StaffFunction> staffFunctionsList = staff.getStaffFunctions();
		List<StaffFunctionDto> staffFunctionsDtoList = new ArrayList<StaffFunctionDto>(staffFunctionsList.size());
		for (StaffFunction staffFunctionIt : staffFunctionsList) {
			StaffFunctionDto staffFunctionDto = new StaffFunctionDto();
			staffFunctionDto.setStaffFunctionName(staffFunctionIt.getStaffFunctionName());
			staffFunctionsDtoList.add(staffFunctionDto);
		}
		staffDto.setStaffFunctions(staffFunctionsDtoList);

		List<StaffAttendance> staffAttendancesList = staff.getStaffAttendances();
		List<StaffAttendanceDto> staffAttendancesDtoList = new ArrayList<StaffAttendanceDto>(staffAttendancesList.size());
		for (StaffAttendance staffAttendanceIt : staffAttendancesList) {
			StaffAttendanceDto staffAttendanceDto = new StaffAttendanceDto();
			staffAttendanceDto.setAttendedHours(staffAttendanceIt.getAttendedHours());
			staffAttendanceDto.setAttendanceDate(staffAttendanceIt.getAttendanceDate());
			staffAttendancesDtoList.add(staffAttendanceDto);
		}
		staffDto.setStaffAttendances(staffAttendancesDtoList);

		List<CourseOffering> courseOfferingList = staff.getCourseOfferings();
		List<CourseOfferingDto> courseOfferingDtoList = new ArrayList<CourseOfferingDto>(courseOfferingList.size());
		for (CourseOffering courseOfferingIt : courseOfferingList) {
			courseOfferingIt.setStaff(null);
			courseOfferingIt.getCourse().setCourse(null);
			courseOfferingIt.getCourse().setCourseOfferings(null);
			courseOfferingIt.getCourse().setCourses(null);
			courseOfferingIt.getCourse().setFiles(null);
			List<StudentClassRecording> studentClassRecordingsList = courseOfferingIt.getStudentClassRecordings();
			BigDecimal studentPerformanceMark = new BigDecimal(0);
			for (StudentClassRecording studentClassRecordingIt : studentClassRecordingsList) {
				studentClassRecordingIt.setStudent(null);
				studentClassRecordingIt.setStudentClassRecordingComments(null);
				studentClassRecordingIt.setCourseOffering(null);
				if (studentClassRecordingIt.getPerformanceMark() != null) {
					studentPerformanceMark.add(studentClassRecordingIt.getPerformanceMark());
				}
				staffDao.detach(studentClassRecordingIt);
			}	

			CourseOfferingDto courseOfferingDto = mapper.map(courseOfferingIt, CourseOfferingDto.class);
			courseOfferingDtoList.add(courseOfferingDto);
			if (!studentClassRecordingsList.isEmpty()) {
				studentPerformanceMark = studentPerformanceMark.divide(BigDecimal.valueOf(studentClassRecordingsList.size()));
			}
			
			courseOfferingDto.setAvrStdPerfMark(studentPerformanceMark);

			staffDao.detach(courseOfferingIt.getCourse());			
			staffDao.detach(courseOfferingIt);
		}
		staffDto.setCourseOfferings(courseOfferingDtoList);

		List<StudentClassRecordingComment> stdClassRecCmtList = staff.getStudentClassRecordingComments();
		List<StudentClassRecordingCommentDto> parentComplainCmtList = new ArrayList<StudentClassRecordingCommentDto>();
		for (StudentClassRecordingComment stdClassRecCmtIt : stdClassRecCmtList) {
			stdClassRecCmtIt.setStaff(null);
			stdClassRecCmtIt.setParent(null);
			stdClassRecCmtIt.setStudentClassRecording(null);
			stdClassRecCmtIt.getCommentType().setStudentClassRecordingComments(null);
			if (EnumCommentType.COMPLAINT_ON_STAFF.getTypeId() == stdClassRecCmtIt.getCommentType().getCommentTypeId()) {
				//Get the report complaints
				//stdClassRecCmtIt.getParentComment();				
				parentComplainCmtList.add(mapper.map(stdClassRecCmtIt, StudentClassRecordingCommentDto.class));
			}
			staffDao.detach(stdClassRecCmtIt);
			staffDao.detach(stdClassRecCmtIt.getCommentType());
		}
		//return only the report complaints from parent
		staffDto.setStudentClassRecordingComments(parentComplainCmtList);
		staffDao.detach(staff);
		return staffDto;
	}

	@Transactional(readOnly=true)
	public StudentDto getStudentReportPerformance(Integer studentId) {
		Student student = null;
		if (studentId != null) {
			student = studentDao.find(studentId);
		}
		if (student == null) {
			return null;
		}
		StudentDto studentDto = new StudentDto();
		/* First Name, Last Name, Gender,
		 * Age in years (Calculated based on Date of Birth), Health Concerns, Health Insurance,
		 * Father First Name, Father Last Name, Father Address, Father Emergency Contact,
		 * Mother First Name, Mother First Name, Mother Last Name, Mother Address, Mother Emergency Contact,
		 * Current Courses List, Current Course Start Date, Current Course End Date,
		 * Current Course Attendance Score (calculated based on attendance hours) for each listed current and historical course,
		 * Performance Score (calculated based on performance mark) for each listed current and historical course,
		 * Attitude Score for each listed current and historical course,
		 * Final Exam Score for each listed current and historical course.
		 * Total Score (Attendance + Performance + Attitude + Final Exam) for each listed current and historical course,
		 * and Parents and Teachers Comments (staff_comment and parent_comment) for each listed current and historical course displayed in chronological order.
		 */
		studentDto.setFirstName(student.getFirstName());
		studentDto.setLastName(student.getLastName());
		studentDto.setGender(student.getGender());
		studentDto.setBirthDate(student.getBirthDate());
		studentDto.setHealthConcerns(student.getHealthConcerns());
		studentDto.setHealthInsuranceNumber(student.getHealthInsuranceNumber());
		//--Father info
		studentDto.setFather(new ParentDto());
		if (student.getFather() != null) {
			studentDto.getFather().setFirstName(student.getFather().getFirstName());
			studentDto.getFather().setLastName(student.getFather().getLastName());
			studentDto.getFather().setEmergencyContact(student.getFather().getEmergencyContact());
			if (student.getFather().getAddress() != null) {
				student.getFather().getAddress().setParents(null);
				student.getFather().getAddress().setSchools(null);
				studentDto.getFather().setAddress(mapper.map(student.getFather().getAddress(), AddressDto.class));
				studentDao.detach(student.getFather().getAddress());
			}
		}
		//--Mother info
		studentDto.setMother(new ParentDto());
		if (student.getMother() != null) {
			studentDto.getMother().setFirstName(student.getMother().getFirstName());
			studentDto.getMother().setLastName(student.getMother().getLastName());
			studentDto.getMother().setEmergencyContact(student.getMother().getEmergencyContact());
			if (student.getMother().getAddress() != null) {
				student.getMother().getAddress().setParents(null);
				student.getMother().getAddress().setSchools(null);
				studentDto.getMother().setAddress(mapper.map(student.getMother().getAddress(), AddressDto.class));
				studentDao.detach(student.getMother().getAddress());
			}
		}
		
		List<StudentClassRecording> studentClassRecordingList = student.getStudentClassRecordings();
		List<StudentClassRecordingDto> studentClassRecordingDtoList = new ArrayList<StudentClassRecordingDto>();
		for (StudentClassRecording studentClassRecordingIt : studentClassRecordingList) {
			StudentClassRecordingDto studentClassRecordingDto = new StudentClassRecordingDto();
			CourseOfferingDto courseOfferingDto = new CourseOfferingDto();
			studentClassRecordingDto.setCourseOffering(courseOfferingDto);
			courseOfferingDto.setCourse(new CourseDto());

			courseOfferingDto.getCourse().setCourseName(studentClassRecordingIt.getCourseOffering().getCourse().getCourseName());
			courseOfferingDto.getCourse().setCourseDescription(studentClassRecordingIt.getCourseOffering().getCourse().getCourseDescription());
			courseOfferingDto.setStartDate(studentClassRecordingIt.getCourseOffering().getStartDate());
			courseOfferingDto.setEndDate(studentClassRecordingIt.getCourseOffering().getEndDate());

			studentClassRecordingDto.setPerformanceMark(studentClassRecordingIt.getPerformanceMark());
			courseOfferingDto.setCourseOfferingStudentEnrolments(new ArrayList<CourseOfferingStudentEnrolmentDto>());
			if (studentClassRecordingIt.getCourseOffering().getCourseOfferingStudentEnrolments() != null
					&& !studentClassRecordingIt.getCourseOffering().getCourseOfferingStudentEnrolments().isEmpty()){
				for (CourseOfferingStudentEnrolment courseOffStdEnr : studentClassRecordingIt.getCourseOffering().getCourseOfferingStudentEnrolments()) {
					CourseOfferingStudentEnrolmentDto courseOffStdEnrDto = new CourseOfferingStudentEnrolmentDto();
					courseOffStdEnrDto.setAttitudeMark(courseOffStdEnr.getAttitudeMark());
					courseOffStdEnrDto.setFinalExamMark(courseOffStdEnr.getFinalExamMark());
					courseOffStdEnrDto.setAttendanceScore(courseOffStdEnr.getAttendanceScore());
					courseOfferingDto.getCourseOfferingStudentEnrolments().add(courseOffStdEnrDto);
				}
			}

			List<StudentClassRecordingComment> stdClassRecCmtList = studentClassRecordingIt.getStudentClassRecordingComments();
			List<StudentClassRecordingCommentDto> commentsList = new ArrayList<StudentClassRecordingCommentDto>();
			for (StudentClassRecordingComment stdClassRecCmtIt : stdClassRecCmtList) {
				stdClassRecCmtIt.setStaff(null);
				stdClassRecCmtIt.setParent(null);
				stdClassRecCmtIt.setStudentClassRecording(null);
				stdClassRecCmtIt.getCommentType().setStudentClassRecordingComments(null);
				commentsList.add(mapper.map(stdClassRecCmtIt, StudentClassRecordingCommentDto.class));
				studentDao.detach(stdClassRecCmtIt);
				studentDao.detach(stdClassRecCmtIt.getCommentType());
			}
			studentClassRecordingDto.setStudentClassRecordingComments(commentsList);
			studentClassRecordingDtoList.add(studentClassRecordingDto);
		}

		studentDto.setStudentClassRecordings(studentClassRecordingDtoList);
		studentDao.detach(student);
		return studentDto;
	}
}